# Copyright 2007 Pete Lypkie, Jonathon Trivett
# This file is part of Luqi
#
# Luqi is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Luqi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Luqi; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


from PIL import Image, ImageChops, ImageEnhance, ImageFilter
from PIL import ImageMath, ImageStat, ImageOps
from math import floor

class CamImage:
    def __init__(self, image):
        """The photo file is specified when creating the class"""
        self._image = image
        
    def adjust(self, x0, y0, x1, y1, x2, y2, x3, y3):
        """The orientation of the game board will be specified
        and the image must be rotated and cropped so that only the
        board is seen and we're looking at it straight-on from above
        (perpendicular to the surface of play)"""
        pointdata = (x0, y0, x1, y1, x2, y2, x3, y3)
        # size was 480x480, but i changed it to 19*26=494
        self._image = self._image.transform((494,494), 
                                            Image.QUAD, 
                                            pointdata,
                                            Image.BICUBIC)
        
    def getavgarray(self):
        """Returns an array of average colors for board positions.
        
        This function is for use on an already-rotated and cropped board pic.
        First you would load this object with an image, then adjust it 
        according to the 4 points chosen by the user.  After that, this 
        function will return a 2d array where each element is a triple that
        is the average colour for a board position.  the triple should be
        (red, green, blue)."""

        imagepix = self._image.load()

        #assumes square input image (%19 pixels will be chopped off each dimension)

        pixperblock = floor(self._image.size[0] / 19)
        side = self._image.size[0]

        #init empty 19x19 rgb pixel array
        pixarray = []
        for x in xrange(19):
            r = []
            for y in xrange(19):
                r += 19*[list(tuple(3*"0"))]
            pixarray += [r]
        
        #sum colour channels in the image , store in pixel array
        for x in xrange(side):
            u = floor(x/pixperblock)
            for y in xrange(side):
                v = floor(y/pixperblock)
                for c in xrange(len(imagepix[x,y])): #for each colour channel
                    pixarray[u][v][c] = float(pixarray[u][v][c]) + float(imagepix[x,y][c])
        
        #average colour channel values
        for x in pixarray:
            for y in x:
                for c in y:
                    c = float(c) / (pixperblock * pixperblock)

    def stones_img(self):
        """return a 2-color image thresholded properly to display stones"""
        
        blackimg = self._image.copy()
#        enhancer = ImageEnhance.Color(blackimg)
#        blackimg = enhancer.enhance(1.4)
        pixellist = list(blackimg.getdata())
        #convert to grayscale by accentuating things that have
        #different green and blue values.  white and black stones will
        #have the same values for green and blue, so they'll both come
        #out black here, making it easy for us to find them using a
        #threshold function.
        pixellist_grayscale = [coloramount(r,g,b) for (r, g, b) in pixellist]
        
        pixellist_grayscale = [threshold(x, 70) for x in pixellist_grayscale]
        
        blackimg = self._image.convert("L")
        blackimg.putdata(pixellist_grayscale)
        #blackimg.save("foo.jpg")
        return blackimg

    def stones_masked(self):
        # expects the stuff to keep to be white.  black stuff in the
        # mask gets removed and replaced with grayimage
        mask = self.stones_img()
        grayimage = Image.new("RGB", mask.size, (100,100,100)) # an image that's all gray100
        masked_image = Image.composite(self._image, grayimage, mask)
        return masked_image


    def gridimage(self):

        # this function does edge detection, showing edges in bright
        # white and everything else in black, but later it should feed
        # the edge-detected image through a hough transform and
        # display the actual lines on the board.
        tmpimage = self._image.copy()
        tmpimage = tmpimage.filter(ImageFilter.CONTOUR)        
        tmpimage = ImageOps.autocontrast(tmpimage, 3)
        tmpimage = tmpimage.convert("L")
        tmpimage = tmpimage.point(lambda x: threshold(x, 127), "L")
        return tmpimage

    
    def boardimage(self):
        """display a grayscale image that shows the board in bright
        white and the rest of the image near-black"""

        #1) find board colour through averaging a section of board
        #2) make a function for finding the difference from the board
        #   colour 
        #3) apply that function to every spot on the board, and
        #   255-difference will be the greyscale colour

        # assume the middle of the picture always has part of the
        # board in it
        center = (self._image.size[0]/2, self._image.size[1]/2)
        region = (center[0]-30, center[1]-30, center[0]+30,
                  center[1]+30)
        boardsection = self._image.crop(region)
        stat = ImageStat.Stat(boardsection)
        
        boardmean = stat.mean
        boardcolor = (floor(boardmean[0]), 
                      floor(boardmean[1]), 
                      floor(boardmean[2]))
        colorimage = Image.new("RGB",self._image.size, boardcolor)
        diffimage = ImageChops.difference(self._image, colorimage)
        diffimage.load()
        diffimage = ImageOps.autocontrast(diffimage, 10)
        grayimage = diffimage.convert("L", (0.333, 0.333, 0.334, 0))
        binaryimage = grayimage.point(lambda x: threshold(x, 70), "L")
        return binaryimage


# end of CamImage class

def threshold(x, lower):
    #inverted threshold to pick out the black stuff.  stuff that's
    #darker than lower should be white, and stuff that's lighter than
    #lower should be black 
    if x < lower:
        return 255
    else:
        return 0



def coloramount(r, g, b):
    # return the difference from white/black/grey
    return 3*(max(g,b)-min(g,b))


